/**
 * Copyright (c) 2012-2013 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.amaze.model;

import com.nokia.example.amaze.ui.MazeCanvas;

/**
 * Model of the marble. 
 */
public class MarbleModel {
    // Constants
    public static final float DEFAULT_SIZE = 4.0f; // Marble width and height
    public static final float DEFAULT_SIZE_HALVED = DEFAULT_SIZE / 2;
    private static final float DEFAULT_Y = 10.0f;
    public static final float HYPOTENUSE = (float)Math.sqrt(DEFAULT_SIZE_HALVED * DEFAULT_SIZE_HALVED + DEFAULT_SIZE_HALVED * DEFAULT_SIZE_HALVED);
    private static final float DEFAULT_DRAG = 0.1f; // Acts as a mass
    public static final float DEFAULT_BOUNCE_DRAG = DEFAULT_DRAG * 0.8f;
    private static final float MAX_VELOCITY_PER_AXIS = 3.5f;
    private static final float FRICTION = 0.2f;
    
    // Members
    private float[] _position = null;
    private float[] _velocity = null;
    private float[] _newVelocity = null;
    private float _drag = DEFAULT_DRAG;
    
    /**
     * Constructor.
     */
    public MarbleModel() {
        _position = new float[3];
        _position[1] = DEFAULT_Y;
        _velocity = new float[3];
        _newVelocity = new float[3];
    }

    /** 
     * @param drag The drag to set ]0.0, 1.0]. This value defines how much the
     *                marble resists the acceleration.
     */
    public void setDrag(final float drag) {
        if (drag > 0 && drag <= 1.0) {
            // Subtract the given mass from 100 so that we don't have to do it
            // later in the calculations
            _drag = drag;
        }
    }

    /** 
     * @return The drag.
     */
    public final float drag() {
        return _drag;
    }

    /** 
     * @param velocity The velocity to set.
     */
    public final void setVelocity(final float[] velocity) {
        _velocity = velocity;
    }

    /** 
     * @return The velocity.
     */
    public final float[] velocity() {
        return _velocity;
    }

    /**
     * Sets the marble position.
     * @param position The new position.
     */
    public final void setPosition(final float[] position) {
        _position = position;
        _position[1] = DEFAULT_Y;
    }

    /**
     * Sets the marble position.
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     */
    public final void setPosition(final float x, final float y, final float z) {
        _position[0] = x;
        //_position[1] = y;
        _position[2] = z;
    }
 
    /**
     * Moves the marble based on the current velocity and the given ticks.
     * @param ticks The milliseconds since last time the method was called.
     */
    public final void move(final int ticks) {
        final float ticksCoefficient = (float)ticks / MazeCanvas.TICKS_COEFFICIENT;
        _position[0] += _velocity[0] * ticksCoefficient;
        _position[2] += _velocity[2] * ticksCoefficient;
    }

    /**
     * Moves the marble.
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     */
    public final void move(final float x, final float y, final float z) {
        _position[0] += x;
        //_position[1] += y;
        _position[2] += z;
    }

    /**
     * @return The marble position.
     */
    public final float[] position() {
        return _position;
    }

    /**
     * Calculates the marble velocity based on the current velocity and the
     * given values of acceleration.
     * @param accelerationX
     * @param accelerationY
     * @param ticks The milliseconds since last time the method was called.
     * @return The modified velocity.
     */
    public final float[] calculateVelocity(final double accelerationX,
                                           final double accelerationY,
                                           final int ticks)
    {
        final float coefficient = _drag * (float)ticks / MazeCanvas.TICKS_COEFFICIENT;
        _newVelocity[1] = _velocity[1];
        _newVelocity[0] = _velocity[0] - (float)accelerationX * coefficient;
        _newVelocity[2] = _velocity[2] + (float)accelerationY * coefficient; // Note: Y => Z
        
        for (int i = 0; i < 3; ++i) {
            if (i == 1) {
                continue;
            }
            
            final float absVelocity = Math.abs(_newVelocity[i]);
            
            // Apply friction
            if (absVelocity < FRICTION) {
                _newVelocity[i] = 0;
            }
            else {
                _newVelocity[i] -= FRICTION * (_newVelocity[i] / absVelocity);
            }
            
            // Limit the velocity based on maximum
            if (absVelocity > MAX_VELOCITY_PER_AXIS) {
                _newVelocity[i] = MAX_VELOCITY_PER_AXIS
                        * (_newVelocity[i] / absVelocity);
            }
        }
        
        return _newVelocity;
    }
}
